package cn.skyquant.quant4j.jforex.strategy;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.forex.ForexConfigSeriesDTO;
import cn.skyquant.quant4j.api.forex.ForexServiceProxy;
import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseConfig;
import cn.skyquant.quant4j.sdk.enums.CloseType;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Pandora extends BaseStrategy implements IBarFeedListener {

    @Configurable("货币")
    public Instrument instrument = Instrument.EURUSD;
    @Configurable("appliedPrice")
    public IIndicators.AppliedPrice appliedPrice = IIndicators.AppliedPrice.WEIGHTED_CLOSE;
    @Configurable("大K线")
    public Period bigPeriod = Period.DAILY;
    @Configurable("价格类型")
    public IIndicators.MaType maType = IIndicators.MaType.EMA;
    @Configurable("短期指标")
    public int short_ma = 19;
    @Configurable("长期指标")
    public int long_ma = 39;
    @Configurable("加仓数列id")
    public int seriesId = 140;
    @Configurable("扛单倍率")
    public double d_atr = 7;
    @Configurable("牛熊指标大于多少做多")
    public double niuxiong_b = 0.002;
    @Configurable("牛熊指标小于多少做多")
    public double niuxiong_s = -0.002;
    @Configurable("牛熊指标仓位增倍")
    public boolean niuxiong_zb = true;
    @Configurable("订单成交日志")
    public boolean log_order_fill = false;
    @Configurable("订单平仓日志")
    public boolean log_order_close = true;
    @Configurable("订单提交日志")
    public boolean log_order_submit = false;
    @Configurable("订单合并日志")
    public boolean log_order_merge = true;
    @Configurable("不开单原因日志")
    public boolean log_order_reason = false;
    @Configurable("订单取消日志")
    public boolean log_order_cancel = false;
    double tp_sum;
    double sl_sum;

    final Map<Instrument, Double> mcclossMap = new HashMap<>();
    final Map<Instrument, Boolean> can_run_map = new HashMap<>();
    final Map<Instrument, Double> atr_map = new HashMap<>();
    Series series;
    ForexServiceProxy forexServiceProxy;
    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体

    @Override
    public final void onStopEx() {
        super.onStopEx();
        series = null;
        forexServiceProxy = null;
        mcclossMap.clear();
        can_run_map.clear();
        atr_map.clear();
    }

    @Override
    protected String getVersion() {
        return "010";
    }

    @Override
    protected String getName() {
        return "Pandora";
    }

    @Override
    protected String getEaName() {
        return "Pandora";
    }

    @Override
    protected void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    protected void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    @Override
    protected void onMessageEx(IMessage message) {
        try {
            IOrder order = message.getOrder();
            if (order == null) {
                return;
            }
            String label = order.getLabel();
            if (label == null) {
                return;
            }
            String[] label_arr = label.split("_");
            if (label_arr.length < 1) {
                return;
            }
            if ((getName()).equalsIgnoreCase(label_arr[0])) {
                OrderEx<PandoraComment> orderEx = new OrderEx(order, new PandoraComment());
                //监听逆势仓的单子
                if (!orderEx.valid(getName())) {
                    return;
                }
                ROnMessage(message, orderEx, order);
            }
        } catch (Throwable e) {
            for (StackTraceElement s : e.getStackTrace()) {
                err("on message,error,%s", s.toString());
            }
        }
    }

    private void ROnMessage(IMessage message, OrderEx<PandoraComment> orderEx, IOrder order) {
        //获取订单的基本参数
        InstrumentEntity ie = new InstrumentEntity(order.getInstrument());
        IAccount account = this.context.getAccount();
        PandoraComment orderComment = orderEx.orderComment;
        int first_equit = orderComment.first_equit;
        int current_equity = (int) account.getEquity();
        int index = orderComment.series_index;
        int seriesId = orderComment.series_id;
        LabelType labelType = orderEx.labelType;
        double openPrice = order.getOpenPrice();
        double closePrice = order.getClosePrice();
        double amount = order.getAmount();
        Instrument instrument = order.getInstrument();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        String instrumentName = order.getInstrument().name();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        int dist = Math.abs(ie.priceToPip(orderEx.orderComment.first_open_price - orderEx.orderComment.open_price));
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if (log_order_merge && sl != 0 && tp != 0) {
                    notif("%s %s, Fill %d-%d Order[%s.%s,amount=%s,order_comment=%s,sl=%s,tp=%s,rate=%s,type=%s]", getName(), getVersion(),
                            seriesId, index, instrumentName, orderCommand, amount, order.getComment(), sl, tp, orderComment.next_add_r, orderComment.type);
                }
                if (log_order_fill && sl == 0 && tp == 0) {
                    out("%s %s, Fill %d-%d Order[%s.%s,amount=%s,open=%s,order_comment=%s,rate=%s,type=%s]", getName(), getVersion(),
                            seriesId, index, instrumentName, orderCommand, amount, openPrice, order.getComment(), orderComment.next_add_r, orderComment.type);
                }
                //如果是加仓订单成交，则修改止盈止损值
                if (labelType == LabelType.N ) {
                    cancelSLAndTP(orderEx);
                }
                break;
            case ORDER_CLOSE_OK:
                if (order.getState() == IOrder.State.CLOSED) {
                    closeLimit(orderEx);
//                    if (log_order_merge && message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_MERGE)) {
//                        out("%s %s, Close by merge %d-%d Order[%s.%s ,amount=%s,open=%s]", getName(), getVersion(),
//                                seriesId, index, instrumentName, orderCommand, amount, openPrice);
//                    }
                    if (order.getProfitLossInUSD() > 0) {
                        tp_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            info("%s %s, %s,Close by TP %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],total_tp=%.2f", getName(), getVersion(),
                                    order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand.name(), amount, openPrice, sl, tp, closePrice, tp_sum);
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.TP, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                    }
                    if (order.getProfitLossInUSD() < 0) {
                        sl_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            err("%s %s, %s, Close by SL %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],total_sl=%.2f", getName(), getVersion(),
                                    order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand.name(), amount, openPrice, sl, tp, closePrice, sl_sum);
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.SL, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                    }
                } else if (order.getState() == IOrder.State.CANCELED) {
                    if (log_order_cancel) {
                        out("%s %s, Cancel Order %d-%d %s [%s]", getName(), getVersion(), orderEx.orderComment.series_id, orderEx.seriesIndex, orderCommand.name(), order.getComment());
                    }
                }
                break;
            case ORDERS_MERGE_OK:
                double last_price = orderEx.orderComment.open_price;
                double last_sl = orderEx.orderComment.sl;
                ForexConfigSeriesDTO seriesDTO = series.get(index);
                ForexConfigSeriesDTO sm = series.get(orderEx.seriesIndex);
                if (sm == null) {
                    break;
                }
                if (!atr_map.containsKey(instrument)) {
                    break;
                }
                double tp_price = seriesDTO.backpip * ie.pipValue;
                double newTP = orderCommand.isLong() ? new BigDecimal(last_price + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(last_price - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
                try {
                    order.setTakeProfitPrice(newTP);
                    order.setStopLossPrice(last_sl);
                } catch (JFException e) {
                    err("order.setTakeProfitPrice error:%s", e.getMessage());
                }
                if (log_order_merge) {
                    notif("%s %s,%s, Merge  %d-%d Order[%s.%s,amount=%s,open_price=%s, avg_price=%s,sl=%s,tp=%s],comment=%s",
                            getName(), getVersion(), order.getProfitLossInUSD(), seriesId, index, instrumentName, orderCommand, amount, orderEx.orderComment.open_price, openPrice, last_sl, newTP, order.getComment());
                }
                break;
            case ORDER_CHANGED_OK:
                //修改止盈止损价成功,判断是不是M仓的止盈止损价都修改为0了
                if (orderEx.labelType == LabelType.M && sl == 0 && tp == 0 && (message.getReasons().contains(IMessage.Reason.ORDER_CHANGED_SL) || message.getReasons().contains(IMessage.Reason.ORDER_CHANGED_TP) )) {
                    if (log_order_merge && log_order_reason) {
                        out("%s %s, %d-%d,Merge sl and tp by Order[%s.%s labelType=%s] ", getName(), getVersion(), seriesId, index, instrumentName, orderCommand, orderEx.labelType);
                    }
                    //进行合并仓位
                    merge(order.getInstrument(), ie, orderEx.name, orderEx.configIndex, orderEx.tradeDirection);
                }
                break;
            case ORDER_SUBMIT_OK:
                if (log_order_submit && isTestMode()) {
                    ITick tick;
                    try {
                        tick = history.getLastTick(order.getInstrument());
                        double currentPrice = orderEx.tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();
                    } catch (JFException e) {
                        err("history.getLastTick error:%s", e.getMessage());
                    }
                }
                break;
            default:
                break;
        }
    }
    //2.修改M仓
    private void cancelSLAndTP(OrderEx orderExNew) {
        //获取同类的M仓
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(orderExNew.instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> sameMList = getSameOrders(orderList, orderExNew.instrument, orderExNew.name, orderExNew.configIndex, orderExNew.tradeDirection, LabelType.M, PandoraComment.class);
        updateSLTP(sameMList, 0, 0);//将所有价格修改为0
    }

    private static List<OrderEx> getSameOrders(List<IOrder> orderList, Instrument instrument, String name, int configIndex,
                                               TradeDirection tradeDirection, LabelType labelType, Class<? extends BaseOrderComment> c) {
        List<OrderEx> result = new ArrayList<>();
        //先遍历所有的订单，找到与当前订单类似的
        for (IOrder order : orderList) {
            if (order == null || order.getInstrument() != instrument || order.getState() != IOrder.State.FILLED) {
                continue;
            }
            BaseOrderComment orderComment;
            if (PandoraComment.class.equals(c)) {
                orderComment = new PandoraComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(name, configIndex) || tradeDirection != orderEx.tradeDirection) {
                continue;
            }
            if (labelType != null && labelType != orderEx.labelType) {
                continue;
            }
            result.add(orderEx);
        }
        result.sort(Comparator.comparingInt(x -> x.seriesIndex));
        return result;
    }

    @Override
    protected void onAccountEx(IAccount account) {
    }

    @Override
    protected void init(InitEntity initEntity) {
        context.subscribeToBarsFeed(instrument, bigPeriod, OfferSide.ASK, this);//逆市运行间隔
        context.subscribeToBarsFeed(instrument, period, OfferSide.ASK, this);//逆市运行间隔
        forexServiceProxy = new ForexServiceProxy(account.getAccountId(), BorkerType.DUKASCOPY);
        PandoraConfig a = new PandoraConfig(getName(), 0, 1, 0, instrument, d_atr, TradeDirection.BOTH.name());//逆趋势
        initEntity.a.add(a);
        series = new Series(seriesId);
        insMap.put(instrument, new InstrumentEntity(instrument));
        IChart chart = context.getChart(instrument);
        if (chart != null) {
            IFeedDescriptor feedDescriptor = chart.getFeedDescriptor();
            if (feedDescriptor != null) {
                feedDescriptor.setPeriod(bigPeriod);
                feedDescriptor.setOfferSide(OfferSide.ASK);
                chart.setFeedDescriptor(feedDescriptor);
                IIndicator mcc = indicators.getIndicator("MCCLOSC");
                chart.add(mcc,new Object[]{short_ma, long_ma, maType.ordinal()});
                chart.repaint();
            }
        }
        initEntity.r = true;
    }

    //删除同方向的挂单
    private void closeLimit(OrderEx closeOrderEx) {
        try {
            List<IOrder> orderList = engine.getOrders(closeOrderEx.instrument);
            for (IOrder order : orderList) {
                OrderEx orderEx = new OrderEx(order, new PandoraComment());
                if (!orderEx.valid(closeOrderEx.name, closeOrderEx.configIndex)) {
                    continue;
                }
                TradeDirection tradeDirection = closeOrderEx.tradeDirection;
                IEngine.OrderCommand o2 = order.getOrderCommand();
                if ((tradeDirection == TradeDirection.LONG && o2 == IEngine.OrderCommand.BUYLIMIT)
                        || (tradeDirection == TradeDirection.SHORT && o2 == IEngine.OrderCommand.SELLLIMIT)) {
                    if (order.getState() == IOrder.State.OPENED) {
                        order.close();
                        order.waitForUpdate(500, IOrder.State.CANCELED);
                    }
                }
            }
        } catch (JFException e) {
            if (log_error)
                out("config[%d] close %s Limit error:", closeOrderEx.configIndex, closeOrderEx.tradeDirection.name());
        }
    }


    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        if (period.equals(bigPeriod)) {
            can_run_map.remove(instrument);
            boolean b = calc(instrument, bar);
            can_run_map.put(instrument, b);
        }
        ITimeDomain timeDomain0;
        ITimeDomain timeDomain1;
        try {
            timeDomain0 = dataService.getOfflineTimeDomain(0);
            timeDomain1 = dataService.getOfflineTimeDomain(-1);
        } catch (JFException e) {
            if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
            return;
        }
        if (timeDomain0 == null || timeDomain1 == null) {
            if (log_error) warn("onBar.getOffline,TimeDomain is null");
            return;
        }
        long time = bar.getTime();
        //停盘时间不干活
        if (timeDomain1.getEnd() + period.getInterval() > time || time > timeDomain0.getStart() - period.getInterval() * 2) {
            return;
        }
        //逆势部分
        if (period.equals(this.period)) {
            configMap.entrySet().stream()
                    .filter(x -> x.getValue().isValid()
                            && x.getValue() instanceof PandoraConfig
                            && getName().equals(((PandoraConfig) x.getValue()).name)
                            && ((PandoraConfig) x.getValue()).instrument == instrument)
                    .forEach(x -> Rprocess((PandoraConfig) x.getValue(), bar.getTime()));
        }
    }

    private boolean calc(Instrument instrument, IBar bar) {
        try {
            long barstart = history.getBarStart(bigPeriod, context.getTime());
            double[] mccloss = indicators.mcclOsc(instrument, bigPeriod, OfferSide.ASK, appliedPrice, short_ma, long_ma, maType, Filter.WEEKENDS, 1, barstart, 0);
            if (mccloss == null || mccloss.length < 0) {
                return false;
            }
            double mcclos = mccloss[0];
            mcclossMap.put(instrument, mcclos);
            double[] atrs = indicators.atr(instrument, bigPeriod, OfferSide.BID, long_ma, Filter.WEEKENDS, 1, barstart, 0);
            if (atrs == null || atrs.length == 0) {
                return false;
            }
            double atr = atrs[0];
            atr_map.put(instrument, atr);
            return true;
        } catch (JFException e) {
            err("calc error:%s", e.getMessage());
            return false;
        }
    }

    //开始运行
    private void Rprocess(PandoraConfig x, long time) {
        if (!can_run_map.containsKey(x.instrument) || !can_run_map.containsKey(x.instrument)) {
            return;
        }
        //找出所有的订单
        List<IOrder> orderList;
        ITick tick;
        try {
            orderList = engine.getOrders(instrument);
            tick = history.getLastTick(instrument);
        } catch (JFException e) {
            err("%s getOrders error:%s", instrument, e.getMessage());
            return;
        }
        if (orderList == null) {
            return;
        }
        InstrumentEntity ie = insMap.get(x.instrument);
        OrderExManager orderExManager = getOrderExManager(x, orderList, PandoraComment.class);
        if (x.tradeDirection == TradeDirection.LONG || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.LONG, orderExManager);
        }
        if (x.tradeDirection == TradeDirection.SHORT || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.SHORT, orderExManager);
        }
    }

    private void RprocessByType(PandoraConfig x, InstrumentEntity ie, ITick tick, TradeDirection tradeDirection, OrderExManager orderExManager) {
        List<OrderEx> orderExList = orderExManager.getOrderExList(tradeDirection);
        List<OrderEx> orderLimitExList = orderExManager.getLimitOrderExList(tradeDirection);
        OrderEx last = orderExManager.getLast(tradeDirection);
        OrderEx first = orderExManager.getFirst(tradeDirection);

        //如果有限价挂单,则不做任何处理
        if (!orderLimitExList.isEmpty()) {
            return;
        }

        //如果空仓则开首仓
        if (orderExList.isEmpty()) {
            //计算暂停时间是否达标
            openFirstOrder(x, tick, tradeDirection);
            return;
        }
        //如果当前有两仓，就不对了，表示没有合并仓位，需要合并仓位
        if (orderExList.size() > 1) {
            merge(x.instrument, ie, x.name, x.index, tradeDirection);
            return;
        }

        //判断有一个正常的仓位
        boolean is_normal = last != null && first != null && last.equals(first) && last.orderComment.type == OpenType.P && last.orderComment instanceof PandoraComment;
        if (is_normal) {
            //成本价以下再加仓
            if ((tradeDirection == TradeDirection.LONG && tick.getAsk() < last.order.getOpenPrice())
                    || (tradeDirection == TradeDirection.SHORT && tick.getBid() > last.order.getOpenPrice())
            ) {
                //如果不是空仓，又没有限价单，则直接开下一仓
                addOrder(x, ie, tradeDirection, tick, last);//加仓
            }
        }

    }

    private void openFirstOrder(PandoraConfig x, ITick tick, TradeDirection tradeDirection) {
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        ForexConfigSeriesDTO sm0 = series.get(0);
        Double mcclos = mcclossMap.get(x.instrument);
        if (sm0 == null || mcclos == null) {
            return;
        }
        if (tradeDirection.isLong() && mcclos < niuxiong_b) {
            return;
        }
        if (tradeDirection.isShort() && mcclos > niuxiong_s) {
            return;
        }
        double currentPrice = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();//当前价格
        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUY : IEngine.OrderCommand.SELL;
        double sl = tradeDirection == TradeDirection.LONG ? (currentPrice - series.totalPip * ie.pipValue) : (currentPrice + series.totalPip);
        double tp_price = sm0.backpip * ie.pipValue;//回调点位
        double tp = tradeDirection == TradeDirection.LONG ? new BigDecimal(currentPrice + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(currentPrice - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
        double amount = sm0.prs * ie.minAmount * (niuxiong_zb?(1+(tradeDirection.isLong()?(mcclos-niuxiong_b)/niuxiong_b:(mcclos-niuxiong_s)/niuxiong_s)):1);
        if(amount<ie.minAmount){
            amount = ie.minAmount;
        }
        int first_equit = (int) account.getEquity();
        PandoraComment orderComment = new PandoraComment(series.seriesId, 0, OpenType.P, first_equit, context.getTime(), 0, sl, currentPrice, currentPrice, currentPrice, 1);
        OrderEx orderEx = new OrderEx(x.name, LabelType.M, x.index, 0, instrument, tradeDirection);
        String comment = orderComment.toString();
        long goodTillTime = 0;
        try {
            engine.submitOrder(orderEx.toString(), x.instrument, orderCommand, amount, 0, 5, sl, tp, goodTillTime, comment);
        } catch (JFException e) {
            warn("config[%d] %s open first order error:%s", x.index, instrument, e.getMessage());
        }
    }

    private void addOrder(
            PandoraConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            ITick tick,         //最后一次报价
            OrderEx<PandoraComment> last       //尾仓
    ) {

        Instrument instrument = x.instrument;
        Double mcclos = mcclossMap.get(x.instrument);
        int index = last.seriesIndex + 1;
        ForexConfigSeriesDTO sm = series.list.get(index);                                             //本次加仓数列行
        double current_price = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();                 //当前价格
        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUYLIMIT : IEngine.OrderCommand.SELLLIMIT;
        double last_price;
        if (last.orderComment.series_index == 0) {
            last_price = last.order.getOpenPrice();//最后一仓开仓的价格
        } else {
            last_price = last.orderComment.open_price;//最后一仓开仓的价格
        }
        double add_price = sm.addpip * ie.pipValue;//当前仓位要求的加仓点距
        double exp_open_price = tradeDirection == TradeDirection.LONG ? last_price - add_price : last_price + add_price;//预期的开仓价格
        double open_price = tradeDirection == TradeDirection.LONG ? new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.UP).doubleValue();

        double last_order_amount = last.order.getAmount();//上一仓数量
        double last_avg_price = last.order.getOpenPrice();//上一仓的平均价格
        double loss_money = ie.getMoney(tradeDirection == TradeDirection.LONG ? open_price - last_avg_price : last_avg_price - open_price, last_order_amount);//到达开仓点位将会损失的钱
        double loss_charge = ie.getCharge(last_order_amount);
        double real_total_loss = loss_money - loss_charge;//真实亏损（加上手续费）
        double tp_price = sm.backpip  * ie.pipValue;//止盈的距离
        double exp_tp_money = sm.tp * x.m * (niuxiong_zb?(1+(tradeDirection.isLong()?(mcclos-niuxiong_b)/niuxiong_b:(mcclos-niuxiong_s)/niuxiong_s)):1);//本次订单希望盈利这么多钱
        double tp_money = exp_tp_money - real_total_loss;//希望盈利的钱-真实亏损的钱= 真正要盈利的钱
        double amount_exp = tp_money / ie.minMoney / (tp_price * ie.pipMulti) * ie.minAmount;//要有这么多仓位，才能在回调这么多点位时，赚到这么多钱
        double amount_open = new BigDecimal(amount_exp - last_order_amount).setScale(ie.amountScale, RoundingMode.HALF_UP).doubleValue();//期望的点位-实际的点位=要开仓的点位
        double sl = last.orderComment.sl;

        //做一个四舍五入,怕首仓时不够
        if (amount_open < ie.minAmount) {
            amount_open = ie.minAmount;
        }

        OrderEx orderEx = new OrderEx(x.name, LabelType.N, x.index, index, x.instrument, tradeDirection);
        String label = orderEx.toString();

        PandoraComment orderComment = new PandoraComment(series.seriesId, index, OpenType.P, last.orderComment.first_equit, last.orderComment.first_time.getTime(), (int) real_total_loss, sl, last.orderComment.first_open_price, last.orderComment.open_price, open_price, 1);
        String comment = orderComment.toString();
        long goodTillTime = 0;
        try {
            goodTillTime = history.getNextBarStart(bigPeriod, context.getTime()) - Period.ONE_MIN.getInterval();
            engine.submitOrder(label, x.instrument, orderCommand, amount_open, open_price, 5, 0, 0, goodTillTime, comment);
        } catch (JFException e) {if (log_error)
            warn("[label=%s,current_price=%s,open_price=%s,amount_open=%s],error=%s", label, current_price, open_price, ie.formatAmout(amount_open), e.getMessage());
        }
    }

    private void merge(Instrument instrument, InstrumentEntity ie, String name, int configIndex, TradeDirection tradeDirection) {
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> mergeOrderExList = getSameOrders(orderList, instrument, name, configIndex, tradeDirection, null, PandoraComment.class);
        List<OrderEx> invalidOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() != 0 || x.order.getStopLossPrice() != 0)
                .collect(Collectors.toList());
        if (!invalidOrderList.isEmpty()) {
            updateSLTP(invalidOrderList, 0, 0);
            return;
        }
        List<IOrder> mergeOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() == 0 && x.order.getStopLossPrice() == 0)
                .map(x -> x.order)
                .collect(Collectors.toList());
        //如果需要合并的订单只有1个，则放弃merge
        if (mergeOrderExList.size() < 2) {
            return;
        }
        OrderEx<PandoraComment> last_N = mergeOrderExList.get(mergeOrderExList.size() - 1); //N仓（最新的仓）
        OrderEx<PandoraComment> orderEx = new OrderEx(last_N.name, LabelType.M, last_N.configIndex, last_N.seriesIndex, last_N.instrument, last_N.tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        int series_index = last_N.orderComment.series_index;
        int series_id = last_N.orderComment.series_id;
        ForexConfigSeriesDTO sm_next = series.get(series_index + 1);

        //需要自己算的就是跟下一仓有关的
        int first_equit = last_N.orderComment.first_equit;
        Date first_time = last_N.orderComment.first_time;
        int this_profitloss = last_N.orderComment.this_profitloss;
        double sl = last_N.orderComment.sl;
        double first_open_price = last_N.orderComment.first_open_price;
        double pre_open_price = last_N.orderComment.pre_open_price;
        double current_open_price = last_N.order.getOpenPrice();
        OpenType openType = OpenType.P;
        if (sm_next == null) {
            //如果加到尾仓，则进行止损
            openType = OpenType.L;
        }
        //下面两个数值，
        PandoraComment orderComment = new PandoraComment(series_id, series_index, openType, first_equit, first_time.getTime(), this_profitloss, sl, first_open_price, pre_open_price, current_open_price, 1);
        String comment = orderComment.toString();
        try {
            IOrder order = engine.mergeOrders(label, comment, mergeOrderList);
            order.waitForUpdate(2000, IOrder.State.FILLED);
        } catch (JFException e) {
            warn("config[%d] %s.%s merge order[%s]-[%s]:%s", configIndex, instrument.name(), tradeDirection.name(), label, comment, e.getMessage());
        }
    }

    //加仓数列
    final class Series {
        final int seriesId;//加仓数列ID
        List<ForexConfigSeriesDTO> list = new ArrayList<>();
        int totalPip;//总

        Series(int seriesId) {
            this.seriesId = seriesId;
            ResultDTO<List<ForexConfigSeriesDTO>> resultDTO = forexServiceProxy.findConfigSeries(seriesId);
            if (!resultDTO.okData() || resultDTO.data.isEmpty()) {
                return;
            }

            list.addAll(resultDTO.data.stream().filter(x -> x.backpip != null && x.backpip != 0 && x.tp != null && x.tp != 0).collect(Collectors.toList()));
            for (ForexConfigSeriesDTO seriesDTO : list) {
                totalPip += seriesDTO.addpip;
            }
        }

        @Override
        public String toString() {
            return "ATRSeries,series.length" + list.size();
        }

        ForexConfigSeriesDTO get(int index) {
            return index < 0 || index > list.size() - 1 ? null : list.get(index);
        }

        int size() {
            return list.size();
        }
    }

    //阿波罗逆市单配置
    final static class PandoraConfig extends BaseConfig {
        final Instrument instrument;//货币
        final double d_atr;//日线atr倍率
        final TradeDirection tradeDirection;//boll倍率

        PandoraConfig(String name, int index, double m, double lossLimit, Instrument instrument, double d_atr, String tradeDirection) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
            this.d_atr = d_atr;
            this.tradeDirection = TradeDirection.parse(tradeDirection);
        }

        @Override
        public String toString() {
            return "Pandora_" + index
                    + "_" + instrument.name()
                    + "_o_" + tradeDirection.name()
                    + "_m_" + m
                    + "d_atr" + d_atr;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m > 0 && d_atr > 0 && tradeDirection != null;
        }
    }

    private static OrderExManager getOrderExManager(BaseConfig baseConfig, List<IOrder> orderList, Class<? extends BaseOrderComment> c) {
        final OrderExManager o = new OrderExManager();
        for (IOrder order : orderList) {
            BaseOrderComment orderComment;
            if (PandoraComment.class.equals(c)) {
                orderComment = new PandoraComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(baseConfig.name, baseConfig.index) || !orderEx.orderComment.valid) {
                continue;
            }

            if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getState() == IOrder.State.FILLED) {
                o.buyList.add(orderEx);
                o.buy_pl_usd += order.getProfitLossInUSD();
                o.buy_pl_pip += order.getProfitLossInPips();
                o.buy_c += order.getCommissionInUSD();
                if (o.buy_first == null || orderEx.seriesIndex < o.buy_first.seriesIndex) {
                    o.buy_first = orderEx;
                }
                if (o.buy_last == null || orderEx.seriesIndex > o.buy_last.seriesIndex) {
                    o.buy_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getState() == IOrder.State.FILLED) {
                o.sellList.add(orderEx);
                o.sell_pl_usd += order.getProfitLossInUSD();
                o.sell_pl_pip += order.getProfitLossInPips();
                o.sell_c += order.getCommissionInUSD();
                if (o.sell_first == null || orderEx.seriesIndex < o.sell_first.seriesIndex) {
                    o.sell_first = orderEx;
                }
                if (o.sell_last == null || orderEx.seriesIndex > o.sell_last.seriesIndex) {
                    o.sell_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT && order.getState() == IOrder.State.OPENED) {
                o.sellLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT && order.getState() == IOrder.State.OPENED) {
                o.buyLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLSTOP && order.getState() == IOrder.State.OPENED) {
                o.sellStopList.add(orderEx);
            }
        }
        return o;
    }

    /**
     * 数列id_第几仓_加仓类型_首仓净值_首仓时间_首仓价格_下一次加仓倍率
     */
    final static class PandoraComment extends BaseOrderComment {
        double sl;                //本次加仓的用于止损的压力支撑价格6
        double first_open_price;    //首仓price   7
        double pre_open_price;      //上一仓price 8
        double open_price;          //当前仓price 9
        double next_add_r;          //下一次加仓点位倍率 10

        PandoraComment() {
            super();
        }

        public void parse(String comment) {
            super.parse(comment);
            if (s.length >= getParamNum()) {
                try {
                    sl = Double.parseDouble(s[6]);
                    first_open_price = Double.parseDouble(s[7]);
                    pre_open_price = Double.parseDouble(s[8]);
                    open_price = Double.parseDouble(s[9]);
                    next_add_r = Double.parseDouble(s[10]);
                    valid = true;
                } catch (Exception e) {
                    valid = false;
                }
            }
        }

        PandoraComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss, double sl, double first_open_price, double pre_open_price, double open_price, double next_add_r) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
            this.sl = sl;
            this.first_open_price = first_open_price;
            this.pre_open_price = pre_open_price;
            this.open_price = open_price;
            this.next_add_r = next_add_r;
        }

        public String toString() {
            return String.format("%s_%s_%s_%s_%s_%s", super.toString(), sl, first_open_price, pre_open_price, open_price, next_add_r);
        }

        @Override
        public int getParamNum() {
            return super.getParamNum() + 5;
        }
    }
}
